# -*- coding: utf-8 -*-
# @Time    : 2024/6/21 13:41
# @Author  : yujiahao
# @File    : 02_pandas_dataform.py
# @description:DataFrame结构（下称DF）


'''

DataFrame 是 Pandas 的重要数据结构之一，也是在使用 Pandas 进行数据分析过程中最常用的结构之一，是一个二维的数组结构，类似二维数组。可以这么说，掌握了 DataFrame 的用法，就拥有了学习数据分析的基本能力。

比如大数据的Spark框架就有DF数据结构，可以类比一下这个学习。

【Spark & Pandas 的DF对比】
    Pandas DataFrame

        设计目标: 主要用于在单机内存中进行数据分析和操作。
        实现方式: 基于 NumPy 数组，提供了高效的内存数据结构。
        使用场景: 适用于小规模数据集的快速探索性数据分析、数据清洗和转换。

        特点:
            单机内存: 数据必须能被加载到单机内存中。
            灵活性: 提供了丰富的操作和方法，如分组、聚合、排序、筛选等。
            性能: 对于小规模数据集，具有非常高的性能。
            易用性: 具有直观的 API，易于上手。
    Spark DataFrame

        设计目标: 主要用于分布式计算，处理大规模数据集。
        实现方式: 基于 Spark 的 RDD（弹性分布式数据集），提供了分布式数据处理能力。
        使用场景: 适用于大规模数据集的分布式数据处理和分析。

        特点:
            分布式计算: 可以处理远超单机内存的数据量。
            延迟计算: 操作是惰性的，只有在触发行动操作（如 collect、show）时才会实际计算。
            与大数据生态系统集成: 可以与 Hadoop、Hive、Cassandra 等大数据工具无缝集成。
            API 相似性: 提供类似于 Pandas 的 API，使得用户可以更容易地从 Pandas 迁移到 Spark。


【认识DataFrame结构】

DataFrame 一个表格型的数据结构，既有行标签（index），又有列标签（columns），它也被称异构数据表，所谓异构，指的是表格中每列的数据类型可以不同，比如可以是字符串、整型或者浮点型等。
你就当他是一个Excel表格理解，有行有列，而且DataFrame 的每一行数据都可以看成一个 Series 结构，只不过，DataFrame 为这些行中每个数据值增加了一个列标签。
因此 DataFrame 其实是从 Series 的基础上演变而来。在数据分析任务中 DataFrame 的应用非常广泛，因为它描述数据的更为清晰、直观。

其结构图示意图，如下所示：

        DataFrame 包含以下部分：

        1. index（索引）：位于 DataFrame 的左侧，用于标识每一行。索引在图中用绿色背景表示。
           - 例如：索引值为 0, 1, 2, 3。

        2. columns（列）：位于 DataFrame 的顶部，用于标识每一列。列在图中用蓝色背景表示。
           - 例如：列名为 'name', 'age', 'gender', 'rating'。

        示例 DataFrame 如下：

                | name | age | gender | rating |
        |-------|------|-----|--------|--------|
        |   0   | 小明 |  28 |   男   |   3.4  |
        |   1   | 小华 |  29 |   女   |   4.6  |
        |   2   | 小亮 |  30 |   男   |   3.5  |
        |   3   | 小红 |  26 |   女   |   2.9  |

        - name 列表示名字
        - age 列表示年龄
        - gender 列表示性别
        - rating 列表示评分


【说明】

同 Series 一样，DataFrame 自带行标签索引，默认为“隐式索引”即从 0 开始依次递增，行标签与 DataFrame 中的数据项一一对应。
上述表格的行标签从 0 到 4，共记录了 4 条数据（图中将行标签省略）。当然你也可以用“显式索引”的方式来设置行标签。

下面对 DataFrame 数据结构的特点做简单地总结，如下所示：

    > DataFrame 每一列的标签值允许使用不同的数据类型；
    > DataFrame 是表格型的数据结构，具有行和列；
    > DataFrame 中的每个数据值都可以被修改。
    > DataFrame 结构的行数、列数允许增加或者删除；
    > DataFrame 有两个方向的标签轴，分别是行标签和列标签；
    > DataFrame 可以对行和列执行算术运算。

【创建DataFrame对象】

    DataFrame 构造方法如下：

        pandas.DataFrame(data=None, index=None, columns=None, dtype=None, copy=False)

        参数说明：

        - data:
            DataFrame 的数据部分，可以是字典、二维数组、Series、DataFrame 或其他可转换为 DataFrame 的对象。
            如果不提供此参数，则创建一个空的 DataFrame。

        - index:
            DataFrame 的行索引，用于标识每行数据。可以是列表、数组、索引对象等。
            如果不提供此参数，则创建一个默认的整数索引。

        - columns:
            DataFrame 的列索引，用于标识每列数据。可以是列表、数组、索引对象等。
            如果不提供此参数，则创建一个默认的整数索引。

        - dtype:
            指定 DataFrame 的数据类型。可以是 NumPy 的数据类型，例如 np.int64、np.float64 等。
            如果不提供此参数，则根据数据自动推断数据类型。

        - copy:
            是否复制数据。默认为 False，表示不复制数据。如果设置为 True，则复制输入的数据。

'''
import numpy as np
import pandas as pd


# todo 1、创建DataFrame对象
def create_df():
    # todo 1) 创建空的DataFrame对象
    print(f'创建一个空个的DF对象:\n{pd.DataFrame()}')

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # todo 2) 列表创建DataFame对象
    # 2.1 使用单一列表或嵌套列表来创建一个 DataFrame。

    data = [1, 2, 3, 4, 5]
    print(f'使用单一列表{pd.DataFrame(data)}')

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 2.2 使用嵌套列表创建 DataFrame 对象
    data = [['Alex', 10], ['Bob', 12], ['Clarke', 13]]
    print(f'使用嵌套列表，默认列名\n{pd.DataFrame(data)}')
    print(f"使用嵌套列表，指定列名\n{pd.DataFrame(data, columns=['Name', 'Age'])}")

    # 2.3 指定数值元素的数据类型
    print(f"指定数值元素的数据类型为 float\n{pd.DataFrame(data, columns=['Name', 'Age'], dtype=str)}")

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # todo 3) 字典嵌套列表创建

    '''data 字典中，键对应的值的元素长度必须相同（也就是列表长度相同）。如果传递了索引，那么索引的长度应该等于数组的长度；
        如果没有传递索引，那么默认情况下，索引将是 range(n)，其中 n 代表数组长度。'''

    data = {'Name': ['Tom', 'Jack', 'Steve', 'Ricky'], 'Age': [28, 34, 29, 42]}
    print(f'使用字典嵌套列表创建，默认标签：\n{pd.DataFrame(data)}')
    print(f"使用字典嵌套列表创建，指定标签：\n{pd.DataFrame(data, index=['rank1', 'rank2', 'rank3', 'rank4'])}")

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # todo 4) 列表嵌套字典创建DataFrame对象

    '''列表嵌套字典可以作为输入数据传递给 DataFrame 构造函数。默认情况下，字典的键被用作列名。如果没有值则为NaN,这里给个指定行列索引的例子'''

    data = [{'a': 1, 'b': 2}, {'a': 5, 'b': 10, 'c': 20}]

    print(
        f"使用字典嵌套列表创建，指定标签：\n{pd.DataFrame(data, index=['first', 'second'], columns=['a', 'b', 'c', '新增列但没值'])}")

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # todo 5) Series创建DataFrame对象

    '''可以传递一个字典形式的 Series，从而创建一个 DataFrame 对象，其输出结果的行索引是所有 index 的合集'''

    s1 = pd.Series(['Alice', 'Bob', 'Charlie'])
    s2 = pd.Series([25, 30, 35])
    s3 = pd.Series(['New York', 'Los Angeles', 'Chicago'])
    df = pd.DataFrame({'Name': s1, 'Age': s2, 'City': s3})
    print('从 Series 创建 DataFrame\n', df)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # todo 6) 从 NumPy 数组创建

    print('从 NumPy 数组创建：提供一个二维 NumPy 数组。\n',
          pd.DataFrame(pd.DataFrame(np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]))))


# todo 2、读取df
def selec_df():
    # 这是一个df
    s1 = pd.Series(['Alice', 'Bob', 'Charlie'])
    s2 = pd.Series([25, 30, 35])
    s3 = pd.Series(['New York', 'Los Angeles', 'Chicago'])
    df = pd.DataFrame({'Name': s1, 'Age': s2, 'City': s3})

    # todo 2.1 列操作
    # 1、通过列索引选取数据列

    print("通过列索引选取数据列\n", df['Name'])

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # todo 2.2 行操作

    # 1、标签索引选取

    '''可以将行标签传递给 loc 函数，来选取数据。'''

    print("通过标签索引选取数据列\n", df.loc[2])

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 2、整数索引选取

    '''通过将数据行所在的索引位置传递给 iloc 函数，也可以实现数据行选取,iloc 允许接受两个参数分别是行和列，参数之间使用“逗号”隔开，但该函数只能接收整数索引。'''

    print("通过整数索引选取\n", df.iloc[2, 1])

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 3 切片操作多行选取

    print("通过切片操作多行选取\n", df[:2])


# todo 3、增删改df
def update_df():
    # 这是一个df
    s1 = pd.Series(['Alice', 'Bob', 'Charlie'])
    s2 = pd.Series([25, 30, 35])
    s3 = pd.Series(['New York', 'Los Angeles', 'Chicago'])
    df = pd.DataFrame({'Name': s1, 'Age': s2, 'City': s3})

    # todo 3.1 列操作

    # 1、通过列索引增加
    df['newClume'] = pd.DataFrame([19.2, 12])
    print("通过列索引增加数据列\n", df)

    # 2、通过insert增加
    # 注意是column参数
    # 数值1代表插入到columns列表的索引位置

    df.insert(1, column='score', value=[91, 90, 75])
    print("通过insert增加\n", df)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 删除df
    # 列索引删除数据列
    # 通过 del 和 pop() 都能够删除 DataFrame 中的数据列。

    # 使用del删除
    del df['score']
    print('使用del删除\n', df)
    # 使用pop方法删除
    df.pop('City')
    print('使用del删除\n', df)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # todo 3.2 行操作

    '''使用 append() 函数，可以将新的数据行添加到 DataFrame 中，该函数会在行末追加数据行
    从 Pandas 1.4.0 开始，append 方法已被弃用，建议改用 concat 方法来追加数据行。
    ignore_index=True 重新按照顺序生成索引'''

    df2 = pd.DataFrame([9, 8, 7, 5, 4, 3, 22])
    df3 = pd.concat([df, df2], ignore_index=True)
    print(df3)

    # 删除 使用drop
    print('使用pop删除\n', df3.drop(0))


# todo 4、常用方法

def df_func():
    df = pd.DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
    # DataFrame 对象有许多属性和方法，用于数据操作、索引和处理，例如：shape、columns、index、head()、tail()、info()、describe()、mean()、sum() 等。

    # todo 4.1 基本属性

    print('形状:', df.shape)  # 形状
    print('列名:', df.columns)  # 列名
    print('索引:', df.index)  # 索引
    print('元素数量:', df.size)  # 元素数量
    print('前几行数据，默认是前 5 行:\n', df.head(2))  # 前几行数据，默认是前 5 行
    print('后几行数据，默认是后 5 行:\n', df.tail(1))  # 后几行数据，默认是后 5 行
    print('数据信息:\n', df.info())  # 数据信息
    print('描述统计信息:\n', df.describe())  # 描述统计信息
    print('求平均值:\n', df.mean())  # 求平均值
    print('求和:\n', df.sum())  # 求和

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')
    # todo 4.2 一些方法

    # 1) T（Transpose）转置
    '''返回 DataFrame 的转置，也就是把行和列进行交换。'''

    d = {'Name': pd.Series(['c语言中文网', '编程帮', "百度", '360搜索', '谷歌', '微学苑', 'Bing搜索']),
         'years': pd.Series([5, 6, 15, 28, 3, 19, 23]),
         'Rating': pd.Series([4.23, 3.24, 3.98, 2.56, 3.20, 4.6, 3.8])}

    # 构建DataFrame
    df = pd.DataFrame(d)

    print('\n转置之前\n', df)
    # 输出DataFrame的转置
    print('\n输出DataFrame的转置\n', df.T)

    # 2) axes

    '''返回一个行标签、列标签组成的列表。'''
    # 输出行、列标签
    print('\n输出行、列标签\n', df.axes)

    # 3) dtypes 返回每一列的数据类型
    print('返回每一列的数据类型\n', df.dtypes)

    # 4) empty
    '''返回一个布尔值，判断输出的数据对象是否为空，若为 True 表示对象为空。'''

    # 判断输入数据是否为空
    print('判断输入数据是否为空', df.empty)
    # 5) ndim
    '''返回数据对象的维数。DataFrame 是一个二维数据结构。'''

    print('DataFrame的维度', df.ndim)

    # 8) values
    '''以 ndarray 数组的形式返回 DataFrame 中的数据。'''

    print('\nDataFrame的数据\n', df.values)

# todo 5、移动df
def df_shift():
    '''
    shift 方法用于对数据框（DataFrame）或系列（Series）中的数据进行位移操作。

    它可以按指定的行或列数移动数据，常用于时间序列分析中，比如计算移动平均值或创建滞后变量。

    语法:
        DataFrame.shift(periods=1, freq=None, axis=0, fill_value=None)

    参数:
        periods: int, 默认值为 1
            表示移动的时间步数。正值表示向前移动（向下移），负值表示向后移动（向上移）。
        freq: DateOffset, timedelta, 或者字符串，默认值为 None
            如果指定了频率，则按指定的频率移动索引。例如，'D' 表示天，'M' 表示月。
        axis: {0 或 'index', 1 或 'columns'}, 默认值为 0
            指定沿哪个轴移动数据。0 或 'index' 表示按行移动，1 或 'columns' 表示按列移动。
        fill_value: 标量值, 默认值为 None
            用于填充移出边界的值。

    返回值:
        返回一个与调用者相同类型的对象，数据已移动。
    '''

    # 创建一个示例 DataFrame
    df = pd.DataFrame({
        'A': [1, 2, 3, 4, 5],
        'B': [10, 20, 30, 40, 50]
    })

    # 示例 1: 向下移动一行
    print("示例 1: 向下移动一行")
    print("改变前:\n", df)
    shifted_df_1 = df.shift(1, fill_value='吼耶')
    print("改变后:\n", shifted_df_1)
    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 示例 2: 向上移动两行
    print("示例 2: 向上移动两行")
    print("改变前:\n", df)
    shifted_df_2 = df.shift(-2)
    print("改变后:\n", shifted_df_2)
    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 示例 3: 指定填充值
    print("示例 3: 指定填充值")
    print("改变前:\n", df)
    shifted_df_3 = df.shift(1, fill_value=0)
    print("改变后:\n", shifted_df_3)
    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 示例 4: 按列移动数据
    print("示例 4: 按列移动数据")
    print("改变前:\n", df)
    shifted_df_4 = df.shift(1, axis=1)
    print("改变后:\n", shifted_df_4)
    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 示例 5: 按日期频率移动
    date_range = pd.date_range(start='2023-01-01', periods=5, freq='D')
    df_with_dates = pd.DataFrame({
        'A': [1, 2, 3, 4, 5],
        'B': [10, 20, 30, 40, 50]
    }, index=date_range)

    print("示例 5: 按日期频率移动")
    print("改变前:\n", df_with_dates)
    shifted_df_5 = df_with_dates.shift(1, freq='D')
    print("改变后:\n", shifted_df_5)
    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

def main():
    # create_df()
    # selec_df()
    # update_df()
    # df_func()
    df_shift()

if __name__ == '__main__':
    main()
